//============================================================================
//  Copyright (c) 1996-2005 Winbond Electronic Corporation
//
//  Module Name:
//    wblinux.c
//
//  Abstract:
//    Linux releated routines
//
//============================================================================
#include "os_common.h"
#include "linux/new_wireless.h"
ULONG
  WBLINUX_MemoryAlloc( OUT PVOID *VirtualAddress, IN UINT Length )
{
#ifndef CONFIG_BOARD_W90N740
   *VirtualAddress = kmalloc( Length, GFP_ATOMIC ); //GFP_KERNEL is not suitable
#else
   //Added by WangJS 2006.9.1,
   //To suit for cache mechanism of W90N740 board, the MSB of assined address must be 1
   *VirtualAddress = (PVOID)((ULONG)kmalloc( Length, GFP_ATOMIC ) | 0x80000000); //GFP_KERNEL is not suitable
#endif
   if( *VirtualAddress == NULL )
     return OS_MEMORY_ALLOC_FAIL;
   return OS_MEMORY_ALLOC_OK;
}

LONG
  EncapAtomicInc( IN PADAPTER Adapter, IN PVOID pAtomic )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   ULONG	ltmp;
   PULONG	pltmp = (PULONG)pAtomic;
   OS_SPIN_LOCK_ACQUIRED( &pWbLinux->AtomicSpinLock );
   (*pltmp)++;
   ltmp = (*pltmp);
   OS_SPIN_LOCK_RELEASED( &pWbLinux->AtomicSpinLock );
   return ltmp;
}

LONG
  EncapAtomicDec( IN PADAPTER Adapter, IN PVOID pAtomic )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   ULONG	ltmp;
   PULONG	pltmp = (PULONG)pAtomic;
   OS_SPIN_LOCK_ACQUIRED( &pWbLinux->AtomicSpinLock );
   (*pltmp)--;
   ltmp = (*pltmp);
   OS_SPIN_LOCK_RELEASED( &pWbLinux->AtomicSpinLock );
   return ltmp;
}

BOOLEAN
  WBLINUX_Initial( IN PADAPTER Adapter )
{
   PWBLINUX pWbLinux = &Adapter->WbLinux;

   OS_SPIN_LOCK_ALLOCATE( &pWbLinux->SpinLock );
   OS_SPIN_LOCK_ALLOCATE( &pWbLinux->AtomicSpinLock );
   pWbLinux->netdev->open = wb35_open;
   pWbLinux->netdev->stop = wb35_close;
   pWbLinux->netdev->hard_start_xmit = wb35_start_xmit;
   pWbLinux->netdev->set_multicast_list = wb35_set_multicast;
   pWbLinux->netdev->get_stats = wb35_netdev_stats;
   pWbLinux->netdev->wireless_handlers = (struct iw_handler_def *)&test_iw_handlers;

   return TRUE;
}

VOID
  WBLinux_ReceivePacket( IN PADAPTER Adapter, IN PRXLAYER1 pRxLayer1 )
{
   PWBLINUX pWbLinux = &Adapter->WbLinux;
   struct sk_buff *skb = NULL;
   PUCHAR		BufAddr;
   ULONG		ByteCount = 0;
   ULONG		i;
   USHORT		BufSize;

   for( i=0; i<pRxLayer1->BufferNumber; i++ )
     {
	BufSize = pRxLayer1->BufferQueue[i].BufferSize;
	ByteCount += BufSize;
     }
#ifdef _PE_RX_DUMP_
   WBDEBUG(( "[w35und]---> Rx total length =%d BuffNum=%d\n", ByteCount, pRxLayer1->BufferNumber ));
#endif

   if( (skb = dev_alloc_skb( ByteCount + 2 )) != NULL )
     {
        skb->dev = pWbLinux->netdev;
        skb_reserve( skb, 2 );
        
	for( i=0; i<pRxLayer1->BufferNumber; i++ )
	  {
	     BufSize = pRxLayer1->BufferQueue[i].BufferSize;
	     BufAddr = pRxLayer1->BufferQueue[i].pBufferAddress;
	     // I Think this fits better - Scythe - 
	     memcpy(skb->data,BufAddr,BufSize);
	     skb_put( skb, BufSize );
	  }

        skb->protocol = eth_type_trans( skb, pWbLinux->netdev );

        netif_rx( skb );
        pWbLinux->netdev->last_rx = jiffies;
	pWbLinux->stats.rx_packets ++;
	pWbLinux->stats.rx_bytes += ByteCount;
     }
   else
     {
#ifdef _PE_RX_DUMP_
	WBDEBUG(( "[w35und]RX SKB ALLOCATE IS FAILED !\n" ));
#endif
     }
}

int wb35_start_xmit( struct sk_buff *skb, struct net_device *netdev )
{
   PADAPTER	Adapter = netdev->priv;
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   PUCHAR		pBufAddress = (PUCHAR)skb->data;
   BOOL		IsStop = FALSE;

#ifdef _PE_TX_DUMP_
   WBDEBUG(( "[w35und]wb35_start_xmit->\n" ));
   WBDEBUG(( "[w35und]skb=%x size=%d\n", skb, skb->len ));
#endif

   // Basic check ---------------------------------------------------
   if( !skb || (skb->len<(DOT_3_TYPE_OFFSET+2)) || pWbLinux->shutdown || !CURRENT_LINK_ON ||
       (
#ifdef _WPA2_
	CURRENT_DESIRED_WPA2_ENABLE &&
#endif //end def _WPA2_
	CURRENT_DESIRED_WPA_ENABLE && CURRENT_CONTROL_PORT_BLOCK && (*(PUSHORT)(pBufAddress+12) != cpu_to_le16(0x8e88)) ) || \
			((psLOCAL->RadioOffStatus.boHwRadioOff == TRUE) || (psLOCAL->RadioOffStatus.boSwRadioOff == TRUE)) )
     {
#ifdef _PE_TX_DUMP_
	WBDEBUG(( "[w35und] wb35_start_xmit SK_BUFF IS NOT OK ! len=%d\n", skb->len ));
#endif
        dev_kfree_skb( skb );
	return 0; // Ignore this packet
     }

   // Check room space ---------------------------------------------
   if( pWbLinux->skb_array[ pWbLinux->skb_SetIndex ] )
     return -EBUSY;

   // Storing skb into skb_array and move point --------------------
   pWbLinux->skb_array[ pWbLinux->skb_SetIndex ] = skb;
   pWbLinux->skb_SetIndex++;
   pWbLinux->skb_SetIndex %= WBLINUX_PACKET_ARRAY_SIZE;

   // Does driver need to stop OS sending?
   if( (pWbLinux->skb_array[pWbLinux->skb_SetIndex]!=NULL) && !pWbLinux->netif_state_stop )
     {
	OS_SPIN_LOCK_ACQUIRED( &pWbLinux->SpinLock );
	if( !pWbLinux->netif_state_stop )
	  {
	     pWbLinux->netif_state_stop = 1;
	     IsStop = TRUE;
	  }
	OS_SPIN_LOCK_RELEASED( &pWbLinux->SpinLock );

	if( IsStop )
	  {
#ifdef _PE_TX_DUMP_
	     WBDEBUG(("[w35und] tx netif stop"));
#endif
	     netif_stop_queue( netdev );
	  }
     }

   // Calling Mds for sending packet --------------------
   Adapter->sLocalPara._NumTxMSDU++;
   netdev->trans_start=jiffies;
   Mds_Tx( Adapter );

   return 0;// return 1 if asking kernel retry to send sk-buffer.
}

VOID
  WBLINUX_GetNextPacket( IN PADAPTER Adapter, IN PDESCRIPTOR pDes )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   struct sk_buff  *pSkb;
   UCHAR 	        *pSkbBuffer;
   int			    buf_size;

   //Get a Linux packet to transmit
   pSkb = pWbLinux->skb_array[ pWbLinux->skb_GetIndex ];
   pWbLinux->skb_array[ pWbLinux->skb_GetIndex ] = NULL;

   // Move the point to the next
   pWbLinux->skb_GetIndex++;
   pWbLinux->skb_GetIndex %= WBLINUX_PACKET_ARRAY_SIZE;

   //================================================================================
   // Fill the related information into descriptor												//
   //================================================================================
   pWbLinux->packet_return = pSkb;
   pSkbBuffer = pSkb->data;
   buf_size = pSkb->len;
   pWbLinux->stats.tx_packets++; // 20060331 move here
   pWbLinux->stats.tx_bytes += buf_size; // 20060331 move here

   //931130.5.t
   //		+------------+
   //		| header + 2 |  <-- DA + SA + T/L
   //		+------------+
   //		+-------------------------------+
   //		| data							|
   //		+-------------------------------+
   //					:
   //					:
   //
   DESCRIPTOR_ADD_BUFFER( pDes, pSkbBuffer, DOT_3_TYPE_OFFSET+2 );
   buf_size -= (DOT_3_TYPE_OFFSET+2);
   pSkbBuffer += (DOT_3_TYPE_OFFSET+2);

   if( buf_size )// Add the left data 931130.5.v
     DESCRIPTOR_ADD_BUFFER( pDes, pSkbBuffer, (USHORT)buf_size );
}

VOID
  WBLINUX_GetNextPacketCompleted( IN PADAPTER Adapter, IN PDESCRIPTOR pDes )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   LONG		room_space = 0;
   struct sk_buff *pSkb;
   BOOL		IsStart = FALSE;

   // Return the send result to LINUX
   pSkb = pWbLinux->packet_return;
   pWbLinux->packet_return = NULL;
   if( in_irq() )
     dev_kfree_skb_irq( pSkb );
   else
     dev_kfree_skb( pSkb );
   MIB_GS_XMIT_OK_INC;

   // Trying to wakeup the netif if necessary
   if( pWbLinux->netif_state_stop )
     {
	room_space = pWbLinux->skb_GetIndex - pWbLinux->skb_SetIndex;
	if( room_space < 0 ) // Ex: + + G + S + + +
	  room_space += WBLINUX_PACKET_ARRAY_SIZE;

	OS_SPIN_LOCK_ACQUIRED( &pWbLinux->SpinLock );
	if( pWbLinux->netif_state_stop )
	  {
	     pWbLinux->netif_state_stop = 0;
	     IsStart = TRUE;
	  }
	OS_SPIN_LOCK_RELEASED( &pWbLinux->SpinLock );

	if( IsStart )
	  {
#ifdef _PE_TX_DUMP_
	     WBDEBUG(("[w35und] tx netif start---"));
#endif
	     netif_wake_queue( pWbLinux->netdev );
	  }
     }
}

struct net_device_stats * wb35_netdev_stats( struct net_device *netdev )
{
   PADAPTER Adapter = (PADAPTER)netdev->priv;
   return &Adapter->WbLinux.stats;
}

void wb35_set_multicast( struct net_device *netdev )
{
   PADAPTER	Adapter = (PADAPTER)netdev->priv;
   phw_data_t	pHwData = &Adapter->sHwData;
   struct dev_mc_list *mcptr;
   UCHAR           Multcst_enable=0;
   int i=0;

   // netif_stop_queue( netdev ); not necessary for 35
   if( netdev->flags & IFF_PROMISC )
     {
	//not support for this mode
#ifdef _PE_STATE_DUMP_
	WBDEBUG(( "[w35und] %s: promiscuous mode", netdev->name ));
#endif
     }
   else if( (netdev->mc_count > DEFAULT_MULTICASTLISTMAX) || (netdev->flags & IFF_ALLMULTI) )
     {
	//not support for this mode
#ifdef _PE_STATE_DUMP_
	WBDEBUG(( "[w35und] %s: allmulti set", netdev->name ));
#endif
     }
   else if( (netdev->flags & IFF_MULTICAST) || (netdev->mc_count != 0) )
     {
	//multicast packet with multicast address
	Multcst_enable=1;
	// set multicast address into regester
	//
	for( mcptr = netdev->mc_list; mcptr; mcptr=mcptr->next )
	  {
	     OS_MEMORY_COPY( Adapter->Mds.MulticastAddressesArray[i],
			     mcptr->dmi_addr, MAC_ADDR_LENGTH );
	     i++;
	  }
	Adapter->Mds.MulticastListNo = (UCHAR)netdev->mc_count;
	hal_set_multicast_address( &Adapter->sHwData,
				   (PUCHAR)Adapter->Mds.MulticastAddressesArray,
				   (UCHAR)Adapter->Mds.MulticastListNo );
     }

   hal_set_accept_multicast( pHwData, Multcst_enable );
   //netif_wake_queue(netdev); not necessary for 35
}

VOID
  WBLINUX_Destroy( IN PADAPTER Adapter )
{
   PWBLINUX pWbLinux = &Adapter->WbLinux;

   DEBUG2("UNregistering....\n");

   WBLINUX_stop( Adapter );
   OS_SPIN_LOCK_FREE( &pWbNdis->SpinLock );
   unregister_netdev( pWbLinux->netdev );
#ifdef _PE_USB_INI_DUMP_
   WBDEBUG(("[w35und] unregister_netdev!\n"));
#endif
}

VOID
  WBLINUX_stop( IN PADAPTER Adapter )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;
   struct sk_buff *pSkb;

   if( OS_ATOMIC_INC( Adapter, &pWbLinux->ThreadCount ) == 1 )
     {
	// Shutdown module immediately
	pWbLinux->shutdown = 1;

	while( pWbLinux->skb_array[ pWbLinux->skb_GetIndex ] )
	  {
	     // Trying to free the un-sending packet
	     pSkb = pWbLinux->skb_array[ pWbLinux->skb_GetIndex ];
	     pWbLinux->skb_array[ pWbLinux->skb_GetIndex ] = NULL;
	     if( in_irq() )
	       dev_kfree_skb_irq( pSkb );
	     else
	       dev_kfree_skb( pSkb );

	     pWbLinux->skb_GetIndex++;
	     pWbLinux->skb_GetIndex %= WBLINUX_PACKET_ARRAY_SIZE;
	  }

#ifdef _PE_STATE_DUMP_
	WBDEBUG(( "[w35und] SKB_RELEASE OK\n" ));
#endif
     }

   OS_ATOMIC_DEC( Adapter, &pWbLinux->ThreadCount );
}

VOID
  WbWlanHalt( IN PADAPTER Adapter )
{
   //netif_stop_queue(Priv.udev);
   //---------------------
   Adapter->sLocalPara.ShutDowned = TRUE;
   //WbNdis51_destroy( Adapter );

   Mds_Destroy( Adapter );

   // Halt
   Roam_Halt(Adapter);
   SCAN_Halt(Adapter);
   MLME_Halt(Adapter);
   SME_Halt(Adapter);

   // For Ts module unload
   //TS_destroy( Adapter );

   // Turn off Rx and Tx hardware ability
   hal_stop( &Adapter->sHwData );
#ifdef _PE_USB_INI_DUMP_
   WBDEBUG(("[w35und] Hal_stop O.K.\n"));
#endif
   OS_SLEEP(100000);// Waiting Irp completed

   // Destroy the NDIS module
   WBLINUX_Destroy( Adapter );

   // Halt the HAL
   hal_halt(&Adapter->sHwData, NULL);
}

BOOL
  WbWLanInitialize( IN PADAPTER Adapter )
{
   phw_data_t	pHwData;
   PUCHAR		pMacAddr, pMacAddr2;
   ULONG		InitStep = 0;
   UCHAR		EEPROM_region;
   UCHAR		HwRadioOff;

   do
     {
	//
	// Setting default value for Linux
	//
	Adapter->sLocalPara.region_INF = REGION_AUTO;
	Adapter->sLocalPara.TxRateMode = RATE_AUTO;
	psLOCAL->bMacOperationMode = MODE_802_11_BG;	// B/G mode
	Adapter->Mds.TxRTSThreshold = DEFAULT_RTSThreshold;
	Adapter->Mds.TxFragmentThreshold = DEFAULT_FRAGMENT_THRESHOLD;
	hal_set_phy_type( &Adapter->sHwData, RF_WB_242_1 );
	Adapter->sLocalPara.MTUsize = MAX_ETHERNET_PACKET_SIZE;
	psLOCAL->bPreambleMode = AUTO_MODE;
	Adapter->sLocalPara.RadioOffStatus.boSwRadioOff = FALSE;
	psSME->bDesiredPowerSave = FALSE;
	psSCAN->BScanEnable = TRUE;
	pHwData = &Adapter->sHwData;
	hal_set_phy_type( pHwData, RF_DECIDE_BY_INF );

	//
	// Initial each module and variable
	//
	if( !WBLINUX_Initial( Adapter ) )
	  {
#ifdef _PE_USB_INI_DUMP_
	     WBDEBUG(("[w35und]WBNDIS initialization failed\n"));
#endif
	     break;
	  }

	// Initial Software variable
	Adapter->sLocalPara.ShutDowned = FALSE;

        //added by ws for wep key error detection
        Adapter->sLocalPara.bWepKeyError= FALSE;//added by ws 02/02/04
	Adapter->sLocalPara.bToSelfPacketReceived = FALSE;
	Adapter->sLocalPara.WepKeyDetectTimerCount= 2 * 100; /// 2 seconds

	// Initial USB hal
	InitStep = 1;
	pHwData = &Adapter->sHwData;
	if( !hal_init_hardware( pHwData, Adapter ) )
	  break;

	EEPROM_region = hal_get_region_from_EEPROM( pHwData );
	if (EEPROM_region != REGION_AUTO)
	  psLOCAL->region = EEPROM_region;
	else
	  {
	     if (psLOCAL->region_INF != REGION_AUTO)
	       psLOCAL->region = psLOCAL->region_INF;
	     else
	       psLOCAL->region = REGION_USA;	//default setting
	  }

	// Get Software setting flag from hal
	Adapter->sLocalPara.boAntennaDiversity = FALSE;
	if( hal_software_set( pHwData ) & 0x00000001 )
	  Adapter->sLocalPara.boAntennaDiversity = TRUE;

	//
	// For TS module
	//
	InitStep = 2;
	if( !TS_initial( Adapter ) )
	  break;

	// For MDS module
	InitStep = 3;
	Mds_initial( Adapter );

	//=======================================
	// Initialize the SME, SCAN, MLME, ROAM
	//=======================================
	//		#ifdef NDIS51_MINIPORT
	//		//For WinXP required
	//		vNetDisconnectInd(Adapter);
	//		#endif
	InitStep = 4;
	SME_Init(Adapter);
	Scan_Init(Adapter);
	if( !MLME_Init(Adapter) )
	  break;

	MLMESetExcludeUnencrypted(Adapter, FALSE);
	MLMESetMaxReceiveLifeTime(Adapter, DEFAULT_MAX_RECEIVE_TIME);

	InitStep = 5;
	Roam_Init(Adapter);

	InitStep = 6;
	//		WbNdis51_initial( Adapter );

	// If no user-defined address in the registry, use the addresss "burned" on the NIC instead.
	pMacAddr = Adapter->sLocalPara.ThisMacAddress;
	pMacAddr2 = Adapter->sLocalPara.PermanentAddress;
	hal_get_permanent_address( pHwData, Adapter->sLocalPara.PermanentAddress );// Reading ethernet address from EEPROM
	if( OS_MEMORY_COMPARE( pMacAddr, "\x00\x00\x00\x00\x00\x00", MAC_ADDR_LENGTH ) ) // Is equal
	  {
	     OS_MEMORY_COPY( pMacAddr, pMacAddr2, MAC_ADDR_LENGTH );
	  }
	else
	  {
	     // Set the user define MAC address
	     hal_set_ethernet_address( pHwData, Adapter->sLocalPara.ThisMacAddress );
	  }

	//get current antenna
	psLOCAL->bAntennaNo = hal_get_antenna_number( pHwData );
#ifdef _PE_STATE_DUMP_
	WBDEBUG(("Driver init, antenna no = %d\n", psLOCAL->bAntennaNo));
#endif
	hal_get_hw_radio_off( pHwData );

	// Disable the antenna diversity.
	//PLMESetAntennaDiversity( Adapter, 0 );

	// Waiting for HAL setting OK
	while( !hal_idle( pHwData ) )
	  OS_SLEEP(10000);

	MTO_Init(Adapter);

	HwRadioOff = hal_get_hw_radio_off( pHwData );
	if (HwRadioOff)
	  psLOCAL->RadioOffStatus.boHwRadioOff = TRUE;
	else
	  psLOCAL->RadioOffStatus.boHwRadioOff = FALSE;

	hal_set_radio_mode( pHwData, (BOOLEAN)(psLOCAL->RadioOffStatus.boSwRadioOff || psLOCAL->RadioOffStatus.boHwRadioOff) );

	if( TS_RUNNING_IN_TESTSTA_MODE )
	  { TS_STARTUP( Adapter ); }
	else
	  {
	     K_MSG	sSmeMsg;
	     sSmeMsg.wMsgType = SMEMSG_SCAN_REQ;
	     sSmeMsg.pMsgPtr = NULL;
	     SME_Entry(Adapter, &sSmeMsg);

	     OS_SLEEP(1000000);	//1 sec
	  }

	hal_driver_init_OK( pHwData ) = 1; // Notify hal that the driver is ready now.
	//set a tx power for reference.....
	sme_set_tx_power_level(Adapter, 12);
	return TRUE;

     }
   while(FALSE);

   switch( InitStep )
     {
	//	case 6: WbNdis51_destroy( Adapter );
      case 5: Roam_Halt( Adapter );
      case 4: MLME_Halt( Adapter );
	SCAN_Halt( Adapter );
	SME_Halt( Adapter );
      case 3: Mds_Destroy( Adapter );
      case 2:
      case 1: WBLINUX_Destroy( Adapter );
	hal_halt( pHwData, NULL );
      case 0: break;
     }

   return FALSE;
}

// 20060802
VOID WBLINUX_ConnectStatus( IN PADAPTER Adapter, IN ULONG flag )
{
   PWBLINUX	pWbLinux = &Adapter->WbLinux;

   pWbLinux->LinkStatus = flag; // OS_DISCONNECTED	or  OS_CONNECTED
   if( flag == OS_DISCONNECTED )
     FourWayClear(Adapter);
}

